#!/usr/bin/python

import sys
import os
if __name__ == '__main__':
    temp_path = sys.path[0]
    sys.path.insert(0, os.path.join(temp_path, '..', 'SmartMeshSDK'))

import Tkinter
import dustGuiLib
import dustFrame
from dustStyle import dustStyle

class GetSetOneValFrame(Tkinter.Frame):
    
    def __init__(self,container,lambda_factory,content,mac,handle_update_set):
        
        Tkinter.Frame.__init__(self,container)
        
        # text
        self.textElem = dustGuiLib.Text(self,width=6,height=1)
        self.textElem.insert(1.0, '')
        self.textElem.grid(row=0,column=0)
        
        # get button
        self.getButton = dustGuiLib.Button(self,
                                           command=lambda_factory(
                                                content['cb_get'],
                                                (
                                                    mac,
                                                )
                                           ),
                                           text="get",)
        self.getButton.grid(row=0,column=1)
        
        # set button
        self.setButton = dustGuiLib.Button(self,
                                    command=lambda_factory(
                                            handle_update_set,
                                            (
                                                mac,
                                                self.textElem,
                                                content['min'],
                                                content['max'],
                                                content['cb_set']
                                            )
                                        ),
                                    text="set",)
        self.setButton.grid(row=0,column=2)
    
    def enable(self):
        self.textElem.configure(state=Tkinter.NORMAL)
        self.getButton.configure(state=Tkinter.NORMAL)
        self.setButton.configure(state=Tkinter.NORMAL)
    
    def update(self,val):
        self.textElem.delete(1.0, Tkinter.END)
        self.textElem.insert(1.0, str(val))
        self.textElem.configure(bg=dustStyle.COLOR_BG)
    
    def disable(self):
        self.textElem.configure(state=Tkinter.DISABLED)
        self.getButton.configure(state=Tkinter.DISABLED)
        self.setButton.configure(state=Tkinter.DISABLED)
 
class SetThreeValFrame(Tkinter.Frame):
    
    def __init__(self,container,lambda_factory,content,mac,handle_setThreeVal_set):
        
        # initialize parent class
        Tkinter.Frame.__init__(self,container)
        
        # text 1
        self.textElem1 = dustGuiLib.Text(self,width=6,height=1)
        self.textElem1.insert(1.0, '')
        self.textElem1.grid(row=0,column=0)
        
        # text 2
        self.textElem2 = dustGuiLib.Text(self,width=6,height=1)
        self.textElem2.insert(1.0, '')
        self.textElem2.grid(row=0,column=1)
        
        # text 3
        self.textElem3 = dustGuiLib.Text(self,width=6,height=1)
        self.textElem3.insert(1.0, '')
        self.textElem3.grid(row=0,column=2)
        
        # set button
        self.setButton = dustGuiLib.Button(self,
                                    command=lambda_factory(
                                            handle_setThreeVal_set,
                                            (
                                                mac,
                                                self.textElem1,
                                                self.textElem2,
                                                self.textElem3,
                                                content['min2'],
                                                content['max2'],
                                                content['min3'],
                                                content['max3'],
                                                content['cb_set']
                                            )
                                        ),
                                    text="set",)
        self.setButton.grid(row=0,column=3)
    
    def enable(self):
        self.textElem1.configure(state=Tkinter.NORMAL)
        self.textElem2.configure(state=Tkinter.NORMAL)
        self.textElem3.configure(state=Tkinter.NORMAL)
        self.setButton.configure(state=Tkinter.NORMAL)
    
    def update(self,(val1,val2,val3)):
        self.textElem1.delete(1.0, Tkinter.END)
        self.textElem1.insert(1.0, str(val1))
        self.textElem2.delete(1.0, Tkinter.END)
        self.textElem2.insert(1.0, str(val2))
        self.textElem3.delete(1.0, Tkinter.END)
        self.textElem3.insert(1.0, str(val3))
    
    def disable(self):
        self.textElem1.configure(state=Tkinter.DISABLED)
        self.textElem2.configure(state=Tkinter.DISABLED)
        self.textElem3.configure(state=Tkinter.DISABLED)
        self.setButton.configure(state=Tkinter.DISABLED)

class dustFrameMoteList(dustFrame.dustFrame):
    '''
    \ingroup guiLib
    
    \brief A frame which displays a list of motes.
    '''
    
    LABEL        = 'LABEL'
    ACTION       = 'ACTION'
    GETSETONEVAL = 'GETSETONEVAL'
    SETTHREEVAL  = 'SETTHREEVAL'
    
    def __init__(self,parentElem,guiLock,columnDesc,frameName="mote list",row=0,column=0):
        
        # store params
        self.columnNames = [c['name'] for c in columnDesc]
        self.columntypes = [c['type'] for c in columnDesc]
        self.moteMacs    = []
        
        # local variables
        self.guiElems = []
        
        # init parent
        dustFrame.dustFrame.__init__(self,parentElem,guiLock,frameName,row,column,
                                          scrollable=True)
        
        # create the header row
        self._addHeaderRow(self.columnNames)
    
    #======================== public ==========================================
    
    def addMote(self,mac,columnvals):
        #print columnvals
        assert(len(columnvals.keys())==len(self.columnNames))
        for k in columnvals.keys():
            assert(k in self.columnNames)
        
        # mote might already be in moteMacs when reconnecting
        if mac not in self.moteMacs:
            self.moteMacs.append(mac)
        
        values = [columnvals[colname] for colname in self.columnNames]
	self._addDataRow(mac,values)
    
    def enableMote(self,mac):
        self._changeActiveMode(mac,True)
    
    def disableMote(self,mac):
        self._changeActiveMode(mac,False)
    
    def update(self,mac,columnname,columnval):
        
        row    = self._macToRowIndex(mac)
        column = self._columnNameToColumnIndex(columnname)
        
        if type(columnval) is float:
            valString = "%.1f"%columnval
        elif type(columnval) is tuple:
            valString = columnval
        else:
            valString = str(columnval)
        
        try:
            self.guiElems[row][column].configure(text=valString)
            if self.guiElems[row][column].cget('bg')==dustStyle.COLOR_PRIMARY2:
                self.guiElems[row][column].configure(bg=dustStyle.COLOR_PRIMARY2_LIGHT)
            else:
                self.guiElems[row][column].configure(bg=dustStyle.COLOR_PRIMARY2)
        except Tkinter.TclError:
            self.guiElems[row][column].update(valString)
    
    def clearColors(self):
        for row in self.guiElems:
            for cell in row:
                cell.configure(bg=dustStyle.COLOR_BG)
    
    #======================== private =========================================
    
    def _changeActiveMode(self,mac,isActive):
        assert isActive in [True,False]
        
        try:
            row    = self._macToRowIndex(mac)
        except KeyError:
            # happens when MAC not in data
            return
        else:
            for column in range(len(self.guiElems[row])):
                try:
                    if isActive:
                        self.guiElems[row][column].configure(state=Tkinter.NORMAL)
                    else:
                        self.guiElems[row][column].configure(state=Tkinter.DISABLED)
                except Tkinter.TclError:
                    # happens when not a widget
                    if isActive:
                        self.guiElems[row][column].enable()
                    else:
                        self.guiElems[row][column].disable()
    
    def _addDataRow(self,mac,contents):
        #print "add data row"
	#print [mac, contents, self.LABEL, self.columntypes]
	self._addRowInternal(['-'.join(['%.2x'%b for b in mac])]+contents,
                             [self.LABEL]+self.columntypes,
                             mac)
    
    def _addHeaderRow(self,contents):
        self._addRowInternal(['mac']+contents,
                             [self.LABEL]+[self.LABEL for i in self.columntypes])
    
    def _addRowInternal(self,contents,coltypes,mac=None):
        
        assert(len(contents)==len(coltypes))
        
        self.guiElems.append([])
        for (content,type) in zip(contents,coltypes):
        
            if   type==self.LABEL:
                temp = dustGuiLib.Label(self.container,
                                     text=str(content),
                                     border=1,
                                     bg=dustStyle.COLOR_BG,
                                     relief=Tkinter.RIDGE,
                                     borderwidth=1,
                                     padx=3,
                                     pady=3)
            
            elif type==self.ACTION:
                temp = dustGuiLib.Button(self.container)
                temp.configure(text=content['text'])
                temp.configure(command=self._lambda_factory(content['callback'],(mac,temp)))
            
            elif type==self.GETSETONEVAL:
                temp = GetSetOneValFrame(self.container,
                                   self._lambda_factory,
                                   content,
                                   mac,
                                   self._handle_getSetOneVal_set)
            
            elif type==self.SETTHREEVAL:
                temp = SetThreeValFrame(self.container,
                                   self._lambda_factory,
                                   content,
                                   mac,
                                   self._handle_setThreeVal_set)
            
            else:
                raise SystemError("column type {0} unsupported".format(type))
            
            self._add(temp,len(self.guiElems)-1,
                           len(self.guiElems[-1]),
                           sticky=Tkinter.W+Tkinter.E)
            self.guiElems[-1].append(temp)
    
    def _handle_getSetOneVal_set(self,mac,textField,min,max,cb):
        
        # retrieve value from text field
        self.guiLock.acquire()
        valString = textField.get(1.0,Tkinter.END)
        self.guiLock.release()
        
        # convert to int
        try:
           val = int(valString)
        except ValueError:
            self.guiLock.acquire()
            textField.configure(bg=dustStyle.COLOR_ERROR)
            self.guiLock.release()
            return
        
        # check boundaries
        if val<min or val>max:
            self.guiLock.acquire()
            textField.configure(bg=dustStyle.COLOR_WARNING)
            self.guiLock.release()
            return
        
        # if you get here, value is accepted
        self.guiLock.acquire()
        textField.configure(bg=dustStyle.COLOR_NOERROR)
        self.guiLock.release()
        
        # call callback
        cb(mac,val)
    
    def _handle_setThreeVal_set(self,mac,textField1,textField2,textField3,min2,max2,min3,max3,cb):
        
        # retrieve value from text fields
        self.guiLock.acquire()
        valString1 = textField1.get(1.0,Tkinter.END)
        self.guiLock.release()

        self.guiLock.acquire()
        valString2 = textField2.get(1.0,Tkinter.END)
        self.guiLock.release()

        self.guiLock.acquire()
        valString3 = textField3.get(1.0,Tkinter.END)
        self.guiLock.release()
        
        # convert to ints
        try:
            val1 = int(valString1)
        except ValueError:
            self.guiLock.acquire()
            textField1.configure(bg=dustStyle.COLOR_ERROR)
            self.guiLock.release()
            return

        try:
            val2 = int(valString2)
        except ValueError:
            self.guiLock.acquire()
            textField2.configure(bg=dustStyle.COLOR_ERROR)
            self.guiLock.release()
            return

        try:
            val3 = int(valString3)
        except ValueError:
            self.guiLock.acquire()
            textField3.configure(bg=dustStyle.COLOR_ERROR)
            self.guiLock.release()
            return
        
        # check boundaries
        if val2<min2 or val2>max2:
            self.guiLock.acquire()
            textField2.configure(bg=dustStyle.COLOR_WARNING)
            self.guiLock.release()
            return
            
        if val3<min3 or val3>max3:
            self.guiLock.acquire()
            textField3.configure(bg=dustStyle.COLOR_WARNING)
            self.guiLock.release()
            return
        
        # if you get here, values are accepted
        
        self.guiLock.acquire()
        textField1.configure(bg=dustStyle.COLOR_NOERROR)
        self.guiLock.release()
        
        self.guiLock.acquire()
        textField2.configure(bg=dustStyle.COLOR_NOERROR)
        self.guiLock.release()
        
        self.guiLock.acquire()
        textField3.configure(bg=dustStyle.COLOR_NOERROR)
        self.guiLock.release()
        
        # call callback
        cb(mac,(val1,val2,val3))
    
    def _lambda_factory(self,fun,param):
        return lambda:fun(*param)
    
    def _macToRowIndex(self,mac):
        i     = 0
        for i in range(len(self.moteMacs)):
            if self.moteMacs[i]==tuple(mac):
                return i+1 # row 0 is used for the heading
        raise KeyError('mac {0} not present in table'.format(mac))
    
    def _columnNameToColumnIndex(self,columnname):
        i     = 0
        for i in range(len(self.columnNames)):
            if self.columnNames[i]==columnname:
                return i+1 # column 0 is used for the MAC address
        raise KeyError('no columnname {0} in table'.format(columnname))
    
    #======================== helpers =========================================

#============================ sample app ======================================
# The following gets called only if you run this module as a standalone app, by
# double-clicking on this source file. This code is NOT executed when importing
# this module is a larger application
#
class exampleApp(object):
    
    def __init__(self):
        columnnames = [ {'name':'state',
                         'type':dustFrameMoteList.LABEL,
                        },
                        {'name':'numDataRx',
                         'type':dustFrameMoteList.LABEL,
                        },
                        {'name':'numIpRx',
                         'type':dustFrameMoteList.LABEL,
                        },
                        {'name':'toggle led',
                         'type':dustFrameMoteList.ACTION,
                        },
                        {'name':'rate',
                         'type':dustFrameMoteList.GETSETONEVAL,
                        },
                        {'name':'pkgen (num/rate/size)',
                         'type':dustFrameMoteList.SETTHREEVAL,
                        },
                        ]
        macMote1    = (0x00,0x17,0x0d,0x00,0x00,0x38,0x11,0x11)
        macMote2    = (0x00,0x17,0x0d,0x00,0x00,0x38,0x22,0x22)
    
        self.window  = dustWindow("dustFrameMoteList",
                                  self._closeCb)
        self.guiLock            = threading.Lock()
        self.frame = dustFrameMoteList(
                                self.window,
                                self.guiLock,
                                columnnames,
                                row=0,column=0)
        self.frame.show()
        
        self.frame.addMote(
                                macMote1,
                                { 
                                    'state':          'operational',
                                    'numDataRx':      100,
                                    'numIpRx':        70,
                                    'toggle led':
                                                    {
                                                        'text':      'ON',
                                                        'callback':  self._toggleLedCb,
                                                    },
                                    'rate':
                                                    {
                                                        'min':       1000,
                                                        'max':       60000,
                                                        'cb_get':    self._rateCbGet,
                                                        'cb_set':    self._rateCbSet,
                                                    },
                                    'pkgen (num/rate/size)':
                                                    {
                                                        'min2':      1000,
                                                        'max2':      60000,
                                                        'min3':      0,
                                                        'max3':      90,
                                                        'cb_set':    self._pkgenCbSet,
                                                    },
                                }
                          )
        
        self.frame.addMote(
                                macMote2,
                                { 
                                    'state':          'searching',
                                    'numDataRx':      0,
                                    'numIpRx':        0,
                                    'toggle led':
                                                    {
                                                        'text':      'ON',
                                                        'callback':  self._toggleLedCb,
                                                    },
                                    'rate':
                                                    {
                                                        'min':       1000,
                                                        'max':       60000,
                                                        'cb_get':    self._rateCbGet,
                                                        'cb_set':    self._rateCbSet,
                                                    },
                                    'pkgen (num/rate/size)':
                                                    {  
                                                        'min2':      16,
                                                        'max2':      60000,
                                                        'min3':      0,
                                                        'max3':      90,
                                                        'cb_set':    self._pkgenCbSet,
                                                    },
                                }
                            )
        
        for i in range(3,40):
            self.frame.addMote(
                            tuple([random.randint(0,255) for i in range(8)]),
                            { 
                                'state':          'searching',
                                'numDataRx':      0,
                                'numIpRx':        0,
                                'toggle led':
                                                {
                                                    'text':      'ON',
                                                    'callback':  self._toggleLedCb,
                                                },
                                'rate':
                                                {
                                                    'min':       1000,
                                                    'max':       60000,
                                                    'cb_get':    self._rateCbGet,
                                                    'cb_set':    self._rateCbSet,
                                                },
                                'pkgen (num/rate/size)':
                                                {  
                                                    'min2':      16,
                                                    'max2':      60000,
                                                    'min3':      0,
                                                    'max3':      90,
                                                    'cb_set':    self._pkgenCbSet,
                                                },
                            }
                        )
        
        
        self.frame.update(macMote1,'numDataRx',            200)
        self.frame.update(macMote1,'numIpRx',              600)
        self.frame.update(macMote1,'rate',                 3000)
        self.frame.update(macMote1,'pkgen (num/rate/size)',(300,3000,80))
        
        self.frame.disableMote(macMote1)
        self.frame.disableMote(macMote2)
        self.frame.enableMote(macMote1)
        
        self.window.mainloop()
    
    def _toggleLedCb(self,mac,button):
        print " _buttonCb for mac {0}".format('-'.join(['%.2x'%c for c in mac]))
        
        if button.cget('text')=='ON':
           button.configure(text="OFF")
        else:
           button.configure(text="ON")
    
    def _rateCbGet(self,mac):
        print " _rateCbGet for mac {0}".format(
                                    '-'.join(['%.2x'%c for c in mac])
                                )
    
    def _rateCbSet(self,mac,val):
        print " _rateCbSet for mac {0}, value={1}".format(
                                    '-'.join(['%.2x'%c for c in mac]),
                                    val
                                )
    def _pkgenCbSet(self,mac,val1,val2,val3):
        print " _pkgenCbSet for mac {0}, value1={1} value2={2} value3={3}".format(
                                    '-'.join(['%.2x'%c for c in mac]),
                                    val1,
                                    val2,
                                    val3
                                )
    
    
    def _closeCb(self):
        print " _closeCb called"

if __name__ == '__main__':
    import threading
    import random
    from dustWindow import dustWindow
    exampleApp()
